'''
功能：动态演示汉诺塔
日期：2018-11-09
'''
import pygame
import sys
from pygame.locals import *
from sys import exit


white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
gold = (239, 229, 51)
blue = (78, 162, 196)
grey = (170, 170, 170)
green = (77, 206, 145)

class Hanoi:
    def __init__(self):

        # 初始化资源
        pygame.init()
        pygame.display.set_caption("Towers of Hanoi")
        self.screen = pygame.display.set_mode((640, 480))
        self.clock = pygame.time.Clock()
        self.framerate = 15

        #定义全局变量
        self.towersMidx = [120, 320, 520]
        self.pointingAt = 0
        self.floater = 0 #盘子号
        self.timeStep = -5
        self.diskL = self.makeDisks()


        #获取汉诺塔的移动轨迹
        self.data =  []
        self.move(5, '0', '1', '2', self.data)
        self.allStep = len(self.data) #移动轨迹的步数

    # 绘制3个塔，每个塔间隔200
    def drawTowers(self):
        for xPos in range(40, 460 + 1, 200):
            pygame.draw.rect(self.screen, green, pygame.Rect(xPos, 400, 160, 20))
            pygame.draw.rect(self.screen, grey, pygame.Rect(xPos + 75, 200, 10, 200))

    # 设置每个圆盘的基本信息
    def makeDisks(self):
        diskL = []
        height = 20
        yPos = 397 - height
        width = 5 * 23
        for i in range(5):
            disk = {}
            disk['rect'] = pygame.Rect(0, 0, width, height)  # 盘子的矩形区域
            disk['rect'].midtop = (120, yPos)  # ypos的位置
            disk['val'] = 5 - i  # 盘子编号,编号越小，盘子越小，再diskL的位置越靠后
            disk['tower'] = 0  # 所在的塔编号
            diskL.append(disk)
            yPos -= height + 3
            width -= 23
        return diskL

    # 绘制盘子
    def drawDisks(self):
        for disk in self.diskL:
            pygame.draw.rect(self.screen, blue, disk['rect'])

    #绘制文字
    def drawText(self,pos,text,color,fontSize):
        fontObj = pygame.font.SysFont('sans serif', fontSize)  # 通过字体文件获得字体对象
        textSurfaceObj = fontObj.render(text, True, color)  # 配置要显示的文字
        textRectObj = textSurfaceObj.get_rect()  # 获得要显示的对象的rect
        textRectObj.center = pos  # 设置显示对象的坐标
        self.screen.blit(textSurfaceObj, textRectObj)  # 绘制字

    def drawBottomText(self):
        for i in range(3):
            self.drawText( (self.towersMidx[i],440), str(i), (255,0,0), 48)

    #重新开始
    def reset(self):
        self.timeStep = -5
        self.diskL = self.makeDisks()

    def show(self):
        while True:
            self.screen.fill(white)
            self.drawBottomText()
            # 绘制顶部文字
            if self.timeStep / 15 < self.allStep:
                self.drawText((320, 60),'Ongoing steps:'+str(self.data[self.timeStep//15][0]) + ' --> '+str(self.data[self.timeStep//15][1]), (255, 0, 0), 38)
            else:
                self.drawText((320, 60), "Please press 'R' key can start again", red, 30)
            self.drawTowers()
            self.drawDisks()
            pygame.display.update()
            #pygame.display.flip()
            self.clock.tick(self.framerate)

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    # 接收到退出事件后退出程序
                    exit()
                #处理键盘事件
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_r:
                        self.reset()
                    if event.key == pygame.K_q:
                        sys.exit()

            self.timeStep += 1
            if self.timeStep / 15 < self.allStep and self.timeStep>=0:
                # 向上运动
                if self.timeStep % 15 == 1:
                    start = int(self.data[self.timeStep // 15][0])
                    for disk in self.diskL[::-1]:
                        if disk['tower'] == start:
                            disk['rect'].midtop = (self.towersMidx[start], 100)
                            self.floater = disk['val']
                            break
                # 平移运动
                elif self.timeStep % 15 == 6:
                    end = int(self.data[self.timeStep // 15][1])
                    for disk in self.diskL:
                        if disk['val'] == self.floater:
                            disk['rect'].midtop = (self.towersMidx[end], 100)
                            break
                # 下落运动
                elif self.timeStep % 15 == 11:
                    end = int(self.data[self.timeStep // 15][1])
                    isExitDisk = False
                    for disk in self.diskL[::-1]:
                        if disk['tower'] == end:
                            isExitDisk = True
                            for moveDisk in self.diskL[::-1]:
                                if moveDisk['val'] == self.floater:
                                    moveDisk['rect'].midtop = (self.towersMidx[end], disk['rect'].top - 23)
                                    moveDisk['tower'] = end
                                    break
                            break
                    #如果该塔上海没有砖块，以最低为准
                    if isExitDisk == False:
                        for moveDisk in self.diskL:
                            if moveDisk['val'] == self.floater:
                                moveDisk['rect'].midtop = (self.towersMidx[end], 377)
                                moveDisk['tower'] = end
                                break
                    else:
                        pass
            # 已经演示完毕，结束
            else:
                pass

    #记录汉诺塔的中间结果
    def move(self,n, a, b, c, data):
        if n == 1:
            data.append([a, c])
            return
        else:
            self.move(n - 1, a, c, b, data)  # 首先需要把 (N-1) 个圆盘移动到 b
            self.move(1, a, b, c, data)  # 将a的最后一个圆盘移动到c
            self.move(n - 1, b, a, c, data)  # 再将b的(N-1)个圆盘移动到c

if __name__ == '__main__':
    hanoi = Hanoi()
    hanoi.show()
